package com.wish233.wiliwilivideo.service.impl;

import cn.dev33.satoken.stp.StpUtil;
import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.json.JSONUtil;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.github.benmanes.caffeine.cache.Cache;
import com.wish233.wiliwiliapi.domain.vo.VideoVO;
import com.wish233.wiliwilicommon.common.ErrorCode;
import com.wish233.wiliwilicommon.constants.CommonConstant;
import com.wish233.wiliwilicommon.exception.BusinessException;
import com.wish233.wiliwilicommon.exception.ThrowUtils;
import com.wish233.wiliwilicommon.utils.SqlUtils;
import com.wish233.wiliwilivideo.domain.dto.video.VideoCreateRequest;
import com.wish233.wiliwilivideo.domain.dto.video.VideoQueryRequest;
import com.wish233.wiliwilivideo.domain.dto.video.VideoUpdateRequest;
import com.wish233.wiliwilivideo.domain.po.Video;
import com.wish233.wiliwilivideo.mapper.VideoMapper;
import com.wish233.wiliwilivideo.mq.MqProducer;
import com.wish233.wiliwilivideo.service.LikesService;
import com.wish233.wiliwilivideo.service.VideoService;
import com.wish233.wiliwilivideo.utils.BloomFilterUtils;
import org.redisson.api.RRateLimiter;
import org.redisson.api.RateIntervalUnit;
import org.redisson.api.RateType;
import org.redisson.api.RedissonClient;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;
import java.util.List;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

import static com.wish233.wiliwilicommon.constants.RedisConstants.*;

/**
 * @author WHH
 * @description 针对表【video(视频表)】的数据库操作Service实现
 * @createDate 2025-03-04 16:39:04
 */
@Service
public class VideoServiceImpl extends ServiceImpl<VideoMapper, Video>
        implements VideoService {

    @Resource
    private RedissonClient redissonClient;
    @Resource
    private StringRedisTemplate stringRedisTemplate;
    @Resource
    private LikesService likesService;
    @Resource
    private BloomFilterUtils bloomFilterUtils;
    @Resource
    private VideoMapper videoMapper;
    @Resource
    private MqProducer mqProducer;
    @Resource
    private Cache<String, VideoVO> videoCache;

    @Override
    public Long createVideo(VideoCreateRequest videoCreateRequest) {
        if (videoCreateRequest == null) {
            throw new BusinessException(ErrorCode.PARAMS_ERROR);
        }
        Video video = BeanUtil.copyProperties(videoCreateRequest, Video.class);
        boolean save = save(video);
        if (!save) {
            throw new BusinessException(ErrorCode.SYSTEM_ERROR);
        }
        //将新生成的视频加入布隆过滤器
        bloomFilterUtils.add(video.getId().toString());
        mqProducer.send2ES(video, 1);
        return video.getId();
    }

    @Override
    public boolean updateVideo(VideoUpdateRequest videoUpdateRequest) {
        if (videoUpdateRequest == null) {
            throw new BusinessException(ErrorCode.PARAMS_ERROR);
        }
        Video video = BeanUtil.copyProperties(videoUpdateRequest, Video.class);
        String key = VIDEO_GET + videoUpdateRequest.getId();
        String videoFromRedis = stringRedisTemplate.opsForValue().get(key);
        //更新视频时删除缓存
        if (StrUtil.isNotBlank(videoFromRedis)) {
            stringRedisTemplate.delete(key);
        }
        mqProducer.send2ES(video, 1);
        return updateById(video);
    }

    @Override
    public boolean deleteVideo(Long videoId) {
        mqProducer.send2ES(videoId, 1);
        return removeById(videoId);
    }

    @Override
    public QueryWrapper<Video> getQueryWrapper(VideoQueryRequest videoQueryRequest) {
        if (videoQueryRequest == null) {
            throw new BusinessException(ErrorCode.PARAMS_ERROR, "请求参数为空");
        }

        QueryWrapper<Video> queryWrapper = new QueryWrapper<>();
        // 精确匹配条件
        queryWrapper.eq(videoQueryRequest.getId() != null, "id", videoQueryRequest.getId());
        queryWrapper.eq(videoQueryRequest.getAuthor() != null, "author", videoQueryRequest.getAuthor());
        queryWrapper.eq(videoQueryRequest.getCategoryId() != null, "category_id", videoQueryRequest.getCategoryId());
        queryWrapper.eq(videoQueryRequest.getStatus() != null, "status", videoQueryRequest.getStatus());

        // 模糊查询条件
        queryWrapper.like(StrUtil.isNotBlank(videoQueryRequest.getTitle()), "title", videoQueryRequest.getTitle());
        queryWrapper.like(StrUtil.isNotBlank(videoQueryRequest.getDescription()), "description", videoQueryRequest.getDescription());

        // 排序处理
        if (SqlUtils.validSortField(videoQueryRequest.getSortField())) {
            boolean isAsc = CommonConstant.SORT_ORDER_ASC.equals(videoQueryRequest.getSortOrder());
            queryWrapper.orderBy(true, isAsc, videoQueryRequest.getSortField());
        }

        return queryWrapper;
    }

    @Override
    public Boolean addViewCount(Long id) {
        //将视频的播放量存入redis，使用原子操作计数，再启用spring定时操作，每10秒同步到数据库
        //同一个用户增加的播放量需要限流
        // 获取Redisson限流器
        String rateLimiterKey = VIDEO_VIEWS_LIMITER_KEY + StpUtil.getLoginId();
        RRateLimiter rateLimiter = redissonClient.getRateLimiter(rateLimiterKey);

        // 初始化限流规则（每10秒1个许可）
        // trySetRate异步设置，参数：限流类型、速率、间隔、时间单位
        rateLimiter.trySetRate(RateType.OVERALL, 1, 5, RateIntervalUnit.SECONDS);

        // 尝试获取许可（设置1秒等待时间，0表示立即返回）
        if (rateLimiter.tryAcquire(1, 0, TimeUnit.SECONDS)) {
            // 6. 获取许可成功，增加播放量
            String key = VIDEO_VIEWS + id;
            stringRedisTemplate.opsForValue().increment(key);
        }
        return true;
    }

    @Override
    public VideoVO getVideoById(Long id) {
        //布隆过滤器不存在直接让他滚 解决混存穿透问题
        ThrowUtils.throwIf(!bloomFilterUtils.contains(id.toString()),
                new BusinessException(ErrorCode.PARAMS_ERROR, "该视频不存在"));
        //三级缓存，caffeine->redis->mysql
        String key = VIDEO_GET + id;
        VideoVO videoVO = videoCache.get(key, s -> {
            String videoFromRedis = stringRedisTemplate.opsForValue().get(key);
            if (StrUtil.isBlank(videoFromRedis)) {
                Video video = getById(id);
                ThrowUtils.throwIf(video == null, new BusinessException(ErrorCode.PARAMS_ERROR, "该视频不存在"));
                VideoVO vo = BeanUtil.copyProperties(video, VideoVO.class);
                stringRedisTemplate.opsForValue().set(key, JSONUtil.toJsonStr(vo), VIDEO_GET_TTL, TimeUnit.MINUTES);
                return vo;
            } else {
                return JSONUtil.toBean(videoFromRedis, VideoVO.class);
            }
        });
        videoVO.setIsLike(likesService.isUserLiked((String) StpUtil.getLoginId(), 1, id));
        return videoVO;
    }

    @Override
    public List<VideoVO> getAllVideo() {
        return videoMapper.getAllVideo().stream()
                .map(video -> BeanUtil.copyProperties(video, VideoVO.class))
                .collect(Collectors.toList());
    }
}




